package gu.sql2java;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;

import static com.google.common.base.Preconditions.*;
import static gu.sql2java.NameUtilities.toCamelCase;

public class ForeignKeyMetaData {
	private final String fkInfo;
	public final String ownerTable;
	public final String name;
	public final String readableName;

	public final ImmutableBiMap<String, String> columnMaps; 
	public enum ForeignKeyRule{
		CASCADE("DELETE"),RESTRICT,SET_NULL("UPDATE"),NO_ACTION,SET_DEFAULT("UPDATE");
		public final String eventOfDeleteRule;

		private ForeignKeyRule(){
			this("");
		}
		private ForeignKeyRule(String event) {
			this.eventOfDeleteRule = event;
		}
		public boolean isNoAction(){
			return NO_ACTION.equals(this) || RESTRICT.equals(this);
		}
		public boolean equals(String value){
			try{
				if(Strings.isNullOrEmpty(value))return false;
				return this == ForeignKeyRule.valueOf(value.toUpperCase());
			}catch(Exception e){
				return false;
			}
		}
	}
	public final ForeignKeyRule updateRule;
	public final ForeignKeyRule deleteRule;
	public final String foreignTable;
	public final boolean selfRef;
	public final ImmutableList<String> columns;
	private volatile int[] columnIds;
	/**
	 * 将{@code input}用分隔符{@code ;,\t\r\f\n}切分为不含空格和分隔符的一组字符串
	 * @param input
	 * @return {@code input}为{@code null}时返回空表
	 */
	private static List<String> elementsOf(String input) {
		List<String> list = new ArrayList<String>();
		if (input != null) {
			StringTokenizer st = new StringTokenizer(input, " ,;\t\n\r\f");
			while (st.hasMoreTokens()) {
				list.add(st.nextToken());
			}
		}
		return list;
	}
	int[] foreignKeyIdArray(Function<String, Integer>columnIdFun){
		// double checking
		if(null == columnIds){
			synchronized (this) {
				if(null == columnIds){
					columnIds = Ints.toArray(Lists.transform(columns, columnIdFun));
				}
			}
		}
		return columnIds;
	}
	ForeignKeyMetaData(String fkInfo, String ownerTable){
		Pattern pattern = Pattern.compile("(\\w+)\\s+\\((.+)\\)\\s+REFERENCES\\s+(\\w+)\\((.+)\\)\\s+(\\w+)\\s+(\\w+)");
		this.fkInfo = fkInfo;
		Matcher m = pattern.matcher(fkInfo);
		checkArgument(m.matches(),"INVALID fkInfo(%s),mismatch REGEX %s",fkInfo,pattern.pattern());
		this.ownerTable = checkNotNull(ownerTable,"ownerTable is null");
		name = m.group(1);
		columns = ImmutableList.copyOf(elementsOf(m.group(2)));
		readableName = toCamelCase(Joiner.on('_').join(columns), true);
		foreignTable = m.group(3);
		List<String> foreignColumns = elementsOf(m.group(4));
		checkArgument(columns.size() == foreignColumns.size(),"MISMATCH foreign key count");
		updateRule = ForeignKeyRule.valueOf(m.group(5));
		deleteRule = ForeignKeyRule.valueOf(m.group(6));
		ImmutableBiMap.Builder<String, String> nameBuilder = ImmutableBiMap.builder();
		for(int i=0;i<columns.size();++i){
			String column = columns.get(i);
			nameBuilder.put(column, foreignTable + "." + foreignColumns.get(i));		
		}
		columnMaps = nameBuilder.build();
		selfRef = foreignTable.equals(ownerTable);
		
	}
	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();
		builder.append("ForeignKeyMetaData [fkInfo=");
		builder.append(fkInfo);
		builder.append(", ownerTable=");
		builder.append(ownerTable);
		builder.append(", foreignTable=");
		builder.append(foreignTable);
		builder.append("]");
		return builder.toString();
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((fkInfo == null) ? 0 : fkInfo.hashCode());
		result = prime * result + ((foreignTable == null) ? 0 : foreignTable.hashCode());
		result = prime * result + ((ownerTable == null) ? 0 : ownerTable.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ForeignKeyMetaData other = (ForeignKeyMetaData) obj;
		if (fkInfo == null) {
			if (other.fkInfo != null)
				return false;
		} else if (!fkInfo.equals(other.fkInfo))
			return false;
		if (foreignTable == null) {
			if (other.foreignTable != null)
				return false;
		} else if (!foreignTable.equals(other.foreignTable))
			return false;
		if (ownerTable == null) {
			if (other.ownerTable != null)
				return false;
		} else if (!ownerTable.equals(other.ownerTable))
			return false;
		return true;
	}
}